using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System.IO;
using System.Threading;

namespace PokerLearner.Poker
{
	// Server class.
	public class PokerServer : PokerManager
	{
        #region Private variables

        static public int DefaultPort = 6767;

        private TcpListener listener;
        private Thread connectThread;
        private Dictionary<Player, PlayerData> playerData;
        private Deck deck;
        private int blinds;		

        #endregion

		#region Dealer

		private int dealer;

		public void MoveDealer()
		{
			dealer = ++dealer % Players.Count;
		}

		/// <summary>
		/// Return a list of players starting with the current dealer
		/// </summary>
		/// <returns></returns>
		public List<Player> GetPlayersFromDealer()
		{
			List<Player> ret = new List<Player>();
			for (int i = 1; i <= Players.Count; i++)
			{
				int index = (dealer + i) % Players.Count;
				ret.Add(Players[index]);
			}
			return ret;
		}

		#endregion

		#region Events

		public event EventHandler Started;
        public event EventHandler Stopped;
		public event EventHandler RoundsStarted;
		public event EventHandler RoundsPaused;

        #endregion

        #region Constructor

        protected PokerServer()
        {
            listener = null;
            connectThread = null;
            playerData = new Dictionary<Player, PlayerData>();
            deck = new Deck();
            blinds = 0;

			paused = true;
        }

        #endregion

		#region Instance

		private static PokerServer instance;

		// The static singleton instance
		public static PokerServer Instance
		{
			get
			{
				if (instance == null)
				{
					instance = new PokerServer();
				}
				return instance;
			}
		}

		#endregion

		#region Server Control

		// Start the server
		public void Start(int startBlinds)
		{
            System.Diagnostics.Debug.Assert(startBlinds > 0);

			blinds = startBlinds;

			listener = new TcpListener(IPAddress.Any, DefaultPort);
			listener.Start();

			connectThread = new Thread(new ThreadStart(ConnectListenerThreadProc));
			connectThread.Start();

			if (Started != null)
			{
				Started(this, null);
			}
		}

		/// Stop the server
		public void Stop()
		{
            if (listener != null)
                listener.Stop();
            if (connectThread != null)
                connectThread.Abort();

			// Call event handler
			if (Stopped != null)
			{
				Stopped(this, null);
			}			
		}

		public void StartRounds()
		{
			StartNewRound(GetPlayersFromDealer());

			if (RoundsStarted != null)
				RoundsStarted(this, null);
		}

		public void PauseRounds()
		{
			paused = true;

			// The event handler is called when the round is finished
		}

		private bool paused;
		public bool Paused
		{
			get { return paused; }
		}

		#endregion	
	
		#region Player Control

		public void ConnectPlayer(Player player, Socket socket)
		{
			// Send a connect packet to every existing client
			SendPacket(Players, PacketType.Connect, player);

			// Add the player for real			
			playerData.Add(player, new PlayerData(socket, new Thread(new ParameterizedThreadStart(ClientStreamThreadProc))));
			AddPlayer(player);

			// Send the connect respone packet to the new player	
			SendPacket(player, PacketType.ConnectResponse, Players);			

			// Start the new client stream listener thread
			playerData[player].ClientThread.Start(player);			
		}

		private void DisconnectPlayer(Player player)
		{
			// Remove the player
			RemovePlayer(player);
			PlayerData removedNet = playerData[player];
			playerData.Remove(player);

			// Notify clients of removed player
			SendPacket(Players, PacketType.Disconnect, player);

			// End the client listener thread
			removedNet.ClientThread.Abort();
		}

		private void KickEmptyPlayers()
		{
			foreach (Player player in Players)
			{
				if (player.Stack == 0)
					SendPacket(player, PacketType.Kick, null);
			}
		}

		#endregion	

		#region Private Round Control
		
		private void StartNewRound(List<Player> availPlayers)
		{			
			if (availPlayers.Count > 1)
			{
				Round = new Round(blinds, availPlayers);

				SendPacket(Players, PacketType.NewRound, Round);

				// Send small blind request
				SendPacket(Round.CurrentPlayer, PacketType.PlayRequest, Round.GetAvailablePlays());

				// Register event handlers
				Round.StateChanged += new EventHandler(Round_StateChanged);
				Round.CurrentPlayerChanged += new EventHandler<PlayerEventArgs>(Round_CurrentPlayerChanged);
				Round.NewPlay += new EventHandler<PlayEventArgs>(Round_NewPlay);

				deck.Shuffle();
				paused = false;

				PokerLogger.Instance.OpenRecords(availPlayers);
			}
		}

		private void EndRound()
		{
			System.Diagnostics.Debug.WriteLine("End Round");

			// Find the winners
			EndRoundEventArgs payload = new EndRoundEventArgs();

			// Add the hands to the payload
			foreach (Player player in Round.Players)
				payload.PlayerHands.Add(player, playerData[player].Hand);


			if (Round.Players.Count == 1)
			{
				payload.Winners.Add(Round.Players[0]);
				payload.PlayerPayoff.Add(Round.Players[0], Round.Pot);
			}
			else
			{
				for (int potIndex = 0; potIndex < Round.SplitPots.Count; potIndex++)
				{
					List<Player> winners = new List<Player>();

					foreach (Player player in Round.GetSplitPotPlayers(potIndex))
					{
						if (winners.Count == 0)
							winners.Add(player);
						else
						{
							if (playerData[player].Hand.CompareTo(playerData[winners[0]].Hand) > 0)
							{
								winners.Clear();
								winners.Add(player);
							}
							else if (playerData[player].Hand.CompareTo(playerData[winners[0]].Hand) == 0)
								winners.Add(player);
						}
					}

					foreach (Player winner in winners)
					{
						System.Diagnostics.Debug.WriteLine(String.Format("Winner {0} split {1}", winner, potIndex));
						int payoff = Round.SplitPots[potIndex] / winners.Count;
						if (!payload.PlayerPayoff.ContainsKey(winner))
							payload.PlayerPayoff.Add(winner, payoff);
						else
							payload.PlayerPayoff[winner] += payoff;

						// Add to round winners
						if (!payload.Winners.Contains(winner))
							payload.Winners.Add(winner);
					}
				}
			}
			
			// Close the logger record and distribute the payoffs
			foreach (Player player in Round.Players)
			{
				int payoff = 0;
				if (payload.Winners.Contains(player))
				{
					player.Stack += payload.PlayerPayoff[player];
					payoff = payload.PlayerPayoff[player];
				}
				PokerLogger.Instance.CloseRecord(player, payoff - Round.PlayerValues[player]);
			}

			// Send the winner packet to all the players connected to the server
			SendPacket(Players, PacketType.EndRound, payload);
			KickEmptyPlayers();

			// Take a break
			Thread.Sleep(2000);

			MoveDealer();
			
			List<Player> availPlayers = GetPlayersFromDealer();

			// Should we pause or start a new round
			if (paused || availPlayers.Count < 2)
			{
				paused = true;
				if (RoundsPaused != null)
					RoundsPaused(this, null);
			}
			else
				StartNewRound(availPlayers);
		}

		
		#endregion

		#region Send / Receive Packet

		protected void SendPacket(Player player, PacketType type, object payload)
		{
			Packet packet = new Packet(type, payload);

			byte[] buffer = new byte[6144];
			MemoryStream memStream = new MemoryStream(buffer, 0, buffer.Length);

			BinaryFormatter formatter = new BinaryFormatter();
			formatter.Serialize(memStream, packet);

			playerData[player].Socket.Send(buffer);
			//System.Diagnostics.Debug.WriteLine(String.Format("Server Thread: {0} Packet: {1} sent to {2}", Thread.CurrentThread.ManagedThreadId, packet, playerData[player].Socket.RemoteEndPoint));
		}

		protected void SendPacket(List<Player> players, PacketType type, object payload)
		{
			Packet packet = new Packet(type, payload);

			byte[] buffer = new byte[6144];
			MemoryStream memStream = new MemoryStream(buffer, 0, buffer.Length);

			BinaryFormatter formatter = new BinaryFormatter();
			formatter.Serialize(memStream, packet);
			foreach (Player player in players)
			{
				playerData[player].Socket.Send(buffer);
				//System.Diagnostics.Debug.WriteLine(String.Format("Server Thread: {0} Packet: {1} sent to {2}", Thread.CurrentThread.ManagedThreadId, packet, playerData[player].Socket.RemoteEndPoint));
			}
		}

		protected Packet RecievePacket(Player player)
		{
			Packet ret = null;

			byte[] buffer = new byte[6144];
			MemoryStream memStream = new MemoryStream(buffer, 0, buffer.Length);

			int num = 0;
			do
			{
				num += playerData[player].Socket.Receive(buffer, num, buffer.Length - num, SocketFlags.None);
			}
			while (num < buffer.Length);

			BinaryFormatter formatter = new BinaryFormatter();
			ret = (Packet)formatter.Deserialize(memStream);

			//System.Diagnostics.Debug.WriteLine(String.Format("Server Thread: {0} Packet: {1} Recieved from {2}", Thread.CurrentThread.ManagedThreadId, ret, playerData[player].Socket.RemoteEndPoint));

			return ret;
		}

		protected Packet RecievePacket(Socket socket)
		{
			Packet ret = null;			
			
			byte[] buffer = new byte[6144];
			MemoryStream memStream = new MemoryStream(buffer, 0, buffer.Length);
			
			int num = 0;
			do
			{
				num += socket.Receive(buffer, num, buffer.Length - num, SocketFlags.None);
			}
			while (num < buffer.Length);
			
			BinaryFormatter formatter = new BinaryFormatter();
			ret = (Packet)formatter.Deserialize(memStream);

			//System.Diagnostics.Debug.WriteLine(String.Format("Server Thread: {0} Packet: {1} Recieved from {2}", Thread.CurrentThread.ManagedThreadId, ret, socket.RemoteEndPoint));

			return ret;
		}

		#endregion

		#region Find Player Hands

		protected void UpdatePlayerHands()
		{
			foreach (Player player in Round.Players)
			{
				playerData[player].Hand = Hand.CreateBestHand(playerData[player].Pocket, Round.CommonCards);
				PokerLogger.Instance.AddHand(player, playerData[player].Hand);
				if(Round.State != RoundState.PreFlop)
					PokerLogger.Instance.AddCommonHand(player, Hand.CreateHand(Round.CommonCards[0], Round.CommonCards[1], 
						Round.CommonCards[2], Round.CommonCards[3], Round.CommonCards[4]));
			}
		}
		
		#endregion

		#region PokerManager Overrides

		public override void RemovePlayer(Player player)
		{
			base.RemovePlayer(player);
			if (Players.Count < 2)
				PauseRounds();
		}

		#endregion

		#region Round Event Handlers

		void Round_CurrentPlayerChanged(object sender, PlayerEventArgs args)
		{
			//System.Diagnostics.Debug.WriteLine("Server Round event Current Player Changed");
			SendPacket(Round.CurrentPlayer, PacketType.PlayRequest, Round.GetAvailablePlays());	
		}

		void Round_StateChanged(object sender, EventArgs e)
		{
			if (Round.State == RoundState.PreFlop)
			{
				foreach (Player player in Round.Players)
				{
					Card[] pocket = new Card[] { deck.GetCard(), deck.GetCard() };
					playerData[player].Pocket = pocket;
					SendPacket(player, PacketType.Pocket, pocket);
				}
				UpdatePlayerHands();
			}
			else if (Round.State == RoundState.Flop)
			{
				Card[] flop = new Card[] { deck.GetCard(), deck.GetCard(), deck.GetCard() };
				Round.SetFlop(flop);
				UpdatePlayerHands();
				SendPacket(Players, PacketType.Flop, flop);

				PokerLogger.Instance.SetNumPlayers(Round.Players.Count);
			}
			else if (Round.State == RoundState.Turn)
			{
				Card turn = deck.GetCard();
				Round.SetTurn(turn);
				UpdatePlayerHands();
				SendPacket(Players, PacketType.Turn, turn);

				PokerLogger.Instance.SetNumPlayers(Round.Players.Count);
			}
			else if (Round.State == RoundState.River)
			{
				Card river = deck.GetCard();
				Round.SetRiver(river);
				UpdatePlayerHands();
				SendPacket(Players, PacketType.River, river);

				PokerLogger.Instance.SetNumPlayers(Round.Players.Count);
			}
			else if (Round.State == RoundState.Complete)
			{			
				EndRound();
			}
		}

		void Round_NewPlay(object sender, PlayEventArgs args)
		{
			if (args.Play.Type == PlayType.Fold)
				PokerLogger.Instance.CloseRecord(args.Play.Player, -Round.PlayerValues[args.Play.Player]);
		}

		#endregion

		#region Connect Listener Thread Proc

		private void ConnectListenerThreadProc()
		{
			Socket socket;
			while (true)
			{
                if (listener == null)
                {
                    break;
                }
				socket = listener.AcceptSocket();
				
				// Read the connect packet
				Packet packet = RecievePacket(socket);
				
				ConnectPlayer((Player)packet.Payload, socket);

				Thread.Sleep(0);
			}			
		}

		#endregion

		#region Client Stream Thread Proc

		private void ClientStreamThreadProc(object obj)
		{
			Player player = (Player)obj;
			
			while (true)
			{
				Packet packet = RecievePacket(player);

				if (packet.Type == PacketType.Disconnect)
				{
					DisconnectPlayer((Player)packet.Payload);
				}
				else if (packet.Type == PacketType.Play)
				{
					System.Diagnostics.Debug.Assert(Round != null);

					Play play = (Play)packet.Payload;
					PokerLogger.Instance.AddPlay(play.Player, play);
					SendPacket(Players, packet.Type, play);
					Round.AddPlay(play);
				}
				Thread.Sleep(0);
			}
			
		}

		#endregion
	}
}